﻿namespace GreenIsomer.Web.Controls.JQuery {
	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Web;
	using System.Web.UI;
	using System.Web.UI.WebControls;

	using Alert = System.Collections.Generic.KeyValuePair<string, JGrowlOptions>;

	/// <summary>
	/// Server control used to generate jGrowl alerts.
	/// </summary>
	[ParseChildren(true), PersistChildren(false)]
	public class JGrowl : JQueryControl {


		#region Constants: Initialization option defaults

		private const bool defaultSticky = false;
		private const JGrowlGlue defaultGlue = JGrowlGlue.After;
		private const string defaultTheme = "default";
		private const string defaultCornersString = "10px";
		private const int defaultCheck = 1000;
		private const int defaultLife = 3000;
		private const Speed defaultSpeed = Speed.Normal;
		private const string defaultEasing = "swing"; //TODO: Again.. the effects...
		private const bool defaultCloser = true;
		private const string defaultCloseTemplate = "&times;";
		private const string defaultCloserTemplate = "<div>[ close all ]</div>";

		#endregion


		#region Static Fields: Property defaults

		//If changing this value, be sure to change the corresponding string constant to match
		private static readonly Unit defaultCorners = new Unit(10, UnitType.Pixel);

		#endregion


		#region Static Fields: Miscellaneous

		private static readonly JGrowlOptions emptyOptions = new JGrowlOptions();

		#endregion


		#region Members

		private List<Alert> alerts;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the GreenIsomer.Web.Controls.JQuery.JGrowl class.
		/// </summary>
		public JGrowl() {
			alerts = new List<Alert>();
		}

		#endregion


		#region Properties: Initialization options

		/// <summary>
		/// Gets or sets an optional header to prefix the message.  This is often helpful for associating messages to each other.
		/// This value can be overriden per message on one or more Alert() method overloads.
		/// This property is HTML encoded and mapped to the "header" option when the alert is initialized.
		/// </summary>
		[DefaultValue(""), Category("Appearance")]
		public virtual string Header {
			get { return ViewState.GetValue("Header", string.Empty); }
			set { ViewState["Header"] = value; }
		}

		/// <summary>
		/// Gets or sets whether a message will stick to the screen until it is intentionally closed by the user.
		/// This value can be overriden per message on one or more Alert() method overloads.
		/// This property maps directly to the "sticky" option when the alert is initialized.
		/// </summary>
		[DefaultValue(defaultSticky), Category("Behavior")]
		public virtual bool Sticky {
			get { return ViewState.GetValue("Sticky", defaultSticky); }
			set { ViewState["Sticky"] = value; }
		}

		/// <summary>
		/// Gets or sets whether a jGrowl notification should be appended to the container after all notifications, or whether it should be prepended to the container before all notifications.
		/// This property maps directly to the "glue" option when the alert is initialized.
		/// </summary>
		[DefaultValue(defaultGlue), Category("Behavior")]
		public virtual JGrowlGlue Glue {
			get { return ViewState.GetValue("Glue", defaultGlue); }
			set { ViewState["Glue"] = value; }
		}

		/// <summary>
		/// Gets or sets a CSS class designating custom styling for this particular message.
		/// This value should be managed by the website project, as jGrowl does not fully support the jQuery UI theme framework.
		/// This value can be overriden per message on one or more Alert() method overloads.
		/// This property maps directly to the "theme" option when the alert is initialized.
		/// </summary>
		[CssClassProperty, DefaultValue(defaultTheme), Category("Appearance")]
		public virtual string Theme {
			get { return ViewState.GetValue("Theme", defaultTheme); }
			set { ViewState["Theme"] = value; }
		}

		/// <summary>
		/// Gets or sets the curvature radius to be used for the notifications as they are created (if rounded corners are available).
		/// This property maps directly to the "corners" option when the alert is initialized.
		/// </summary>
		[DefaultValue(typeof(Unit), defaultCornersString), Category("Appearance")]
		public virtual Unit Corners {
			get { return ViewState.GetValue("Corners", defaultCorners); }
			set { ViewState["Corners"] = value; }
		}

		/// <summary>
		/// Gets or sets the frequency (in milliseconds) that jGrowl should check for messages to be scrubbed from the screen.
		/// This property maps directly to the "check" option when the alert is initialized.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">Value must be greater than zero.</exception>
		[DefaultValue(defaultCheck), Category("Behavior")]
		public virtual int Check {
			get { return ViewState.GetValue("Check", defaultCheck); }
			set {
				if(value <= 0) throw new ArgumentOutOfRangeException("value", "Value must be greater than zero");

				ViewState["Check"] = value;
			}
		}

		/// <summary>
		/// Gets or sets the lifespan (in milliseconds) of a non-sticky message on the screen.
		/// This value can be overriden per message on one or more Alert() method overloads.
		/// This property maps directly to the "life" option when the alert is initialized.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">Value must be greater than zero.</exception>
		[DefaultValue(defaultLife), Category("Behavor")]
		public virtual int Life {
			get { return ViewState.GetValue("Life", defaultLife); }
			set {
				if(value <= 0) throw new ArgumentOutOfRangeException("value", "Value must be greater than zero");

				ViewState["Life"] = value;
			}
		}

		/// <summary>
		/// Gets or sets the standard jQuery animation speed used to open or close a notification.
		/// This property maps directly to the "speed" option when the alert is initialized.
		/// </summary>
		[DefaultValue(defaultSpeed), Category("Behavior")]
		public virtual Speed Speed {
			get { return ViewState.GetValue("Speed", defaultSpeed); }
			set { ViewState["Speed"] = value; }
		}

		/// <summary>
		/// Gets or sets the jQuery easing method to be used with the animation for opening and closing a notification.
		/// This property maps directly to the "easing" option when the alert is initialized.
		/// </summary>
		[DefaultValue(defaultEasing), Category("Behavior")]
		public virtual string Easing {
			get { return ViewState.GetValue("Easing", defaultEasing); }
			set { ViewState["Easing"] = value; }
		}

		/// <summary>
		/// Gets or sets whether or not the close-all button should be used when more then one notification appears on the screen.
		/// This property maps directly to the "closer" option when the alert is initialized.
		/// </summary>
		[DefaultValue(defaultCloser), Category("Appearance")]
		public virtual bool Closer {
			get { return ViewState.GetValue("Closer", defaultCloser); }
			set { ViewState["Closer"] = value; }
		}

		/// <summary>
		/// Gets or sets the HTML content used for the individual notification close links that are added to the corner of a notification.
		/// HTML is accepted, so HTML encoding is required!
		/// This property maps directly to the "closeTemplate" option when the alert is initialized.
		/// </summary>
		[DefaultValue(defaultCloseTemplate), Category("Appearance"), PersistenceMode(PersistenceMode.InnerProperty)]
		public virtual string CloseTemplate {
			get { return ViewState.GetValue("CloseTemplate", defaultCloseTemplate); }
			set { ViewState["CloseTemplate"] = value; }
		}

		/// <summary>
		/// Gets or sets the HTML content used for the close-all link that is added to the bottom of a jGrowl container when it contains more than one notification and Closer is set to true.
		/// HTML is accepted, so HTML encoding is required!
		/// This property maps directly to the "closerTemplate" option when the alert is initialized.
		/// </summary>
		[DefaultValue(defaultCloserTemplate), Category("Appearance"), PersistenceMode(PersistenceMode.InnerProperty)]
		public virtual string CloserTemplate {
			get { return ViewState.GetValue("CloserTemplate", defaultCloserTemplate); }
			set { ViewState["CloserTemplate"] = value; }
		}

		#endregion


		#region Methods: Alert

		/// <summary>
		/// Register an alert to be called on the client on the next render.
		/// </summary>
		/// <param name="message">The message to pop up in the jGrowl alert on the client.</param>
		public void Alert(string message) {
			Alert(message, emptyOptions);
		}

		/// <summary>
		/// Register an alert to be called on the client on the next render.
		/// </summary>
		/// <param name="message">The message to pop up in the jGrowl alert on the client.</param>
		/// <param name="sticky">An override value for the control Sticky property for this alert only.</param>
		public void Alert(string message, bool sticky) {
			JGrowlOptions options = new JGrowlOptions();
			options.Sticky = sticky;
			Alert(message, options);
		}

		/// <summary>
		/// Register an alert to be called on the client on the next render.
		/// </summary>
		/// <param name="message">The message to pop up in the jGrowl alert on the client.</param>
		/// <param name="header">An override value for the control Header property for this alert only.</param>
		public void Alert(string message, string header) {
			JGrowlOptions options = new JGrowlOptions();
			options.Header = header;
			Alert(message, options);
		}

		/// <summary>
		/// Register an alert to be called on the client on the next render.
		/// </summary>
		/// <param name="message">The message to pop up in the jGrowl alert on the client.</param>
		/// <param name="header">An override value for the control Header property for this alert only.</param>
		/// <param name="sticky">An override value for the control Sticky property for this alert only.</param>
		public void Alert(string message, string header, bool sticky) {
			JGrowlOptions options = new JGrowlOptions();
			options.Header = header;
			options.Sticky = sticky;
			Alert(message, options);
		}

		/// <summary>
		/// Register an alert to be called on the client on the next render.
		/// </summary>
		/// <param name="message">The message to pop up in the jGrowl alert on the client.</param>
		/// <param name="options">Advanced options for the message that can override settings on the control (for this alert only).</param>
		public virtual void Alert(string message, JGrowlOptions options) {
			if(message == null) throw new ArgumentNullException("message");
			if(message.Length == 0) throw new ArgumentException("Value is required.", "message");
			if(options == null) throw new ArgumentNullException("options");

			alerts.Add(new Alert(message, options));
		}

		#endregion


		#region Methods: Rendering hooks

		/// <summary>
		/// Allows a jQuery server control to reference scripts and stylesheets necessary to render their client markup and widgets.
		/// </summary>
		/// <param name="manager">The GreenIsomer.Web.Controls.JQuery.Manager used to handle style and script referencing in addition to unified generation of load and ready scripts.</param>
		protected override void Register(Manager manager) {

			manager.RegisterScriptFile(Settings.Default.CoreScriptFile);
			manager.RegisterScriptFile(Settings.Default.JGrowlScriptFile);

			manager.RegisterStyleFile(Settings.Default.JGrowlStyleFile);

			manager.RegisterControl(this);
		}

		/// <summary>
		/// Renders the load script required to generate the client-side jQuery widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected override void RenderLoadScript(ScriptBuilder script) {

			foreach(Alert alert in alerts) {

				script.Append("$.jGrowl(");
				script.AppendValue(HttpUtility.HtmlEncode(alert.Key));

				PropertyCollection properties = CreateProperties(alert.Value);
				if(properties.Count != 0) {
					script.Append(", ");
					script.RenderProperties(properties);
				}

				script.Append(");");
				script.NewLine();
			}

		}

		/// <summary>
		/// Creates the properties object that will be rendered as the jQuery UI initialization options.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="options">The GreenIsomer.Web.Controls.JQuery.JGrowlOptions associated with an Alert() called before render began.</param>
		/// <returns>A GreenIsomer.Web.Controls.JQuery.Properties object that will become the jQuery UI initialization options.</returns>
		protected virtual PropertyCollection CreateProperties(JGrowlOptions options) {
			PropertyCollection properties = base.CreateProperties();

			string header = options.Header ?? Header;
			if(!string.IsNullOrEmpty(header)) properties.AddValue("header", header, true);

			bool sticky = options.Sticky ?? Sticky;
			if(sticky != defaultSticky) properties.AddValue("sticky", sticky);

			JGrowlGlue glue = Glue;
			if(glue != defaultGlue) properties.AddEnumValue("glue", glue);

			string theme = options.Theme ?? Theme;
			if(theme != defaultTheme) properties.AddValue("theme", theme);

			Unit corners = Corners;
			if(corners != defaultCorners) properties.AddValue("corners", corners.ToString());

			int check = Check;
			if(check != defaultCheck) properties.AddValue("check", check);

			int life = options.Life ?? Life;
			if(life != defaultLife) properties.AddValue("life", life);

			Speed speed = Speed;
			if(speed != defaultSpeed) properties.AddEnumValue("speed", speed);

			string easing = Easing;
			if(easing != defaultEasing) properties.AddValue("easing", easing);

			bool closer = Closer;
			if(closer != defaultCloser) properties.AddValue("closer", closer);

			string closeTemplate = CloseTemplate;
			if(closeTemplate != defaultCloseTemplate) properties.AddValue("closeTemplate", closeTemplate);

			string closerTemplate = CloserTemplate;
			if(closerTemplate != defaultCloserTemplate) properties.AddValue("closerTemplate", closerTemplate);

			return properties;
		}

		#endregion


	}

}